package pl.ps.wi.monitoring.station;

import java.io.*;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Vector;

import pl.ps.wi.monitoring.station.enums.AlertEvents;

/**
* @date 2010.04.18
* @version 1.00.00
*/

/**
* @brief Zawiera definicję klasy DBConnector, odpowiedzialnej za połączenie oraz operacje na bazie danych.
*
* Klasa do obsługi bazy danych, umożliwia takie opcje jak: łączenie, zamykanie połączenia,
* wstawianie znacznika czasowego, wstawianie obrazków, wstawianie temperatury z czujników, wstawianie stanu karty, weryfikowanie karty,
* wybieranie aktywnych urządzeń, kamer, czujników itp.
*/
public class DBConnector {
/** 
* Obiekt typu Connection będący referencją do połączenia, jest ustawiony na null jeżeli nie ma połączenia
*/
	private Connection con = null;
/** 
* Obiekt typu LocalConfiguration będący referencją do ustawień połączenia(login, hasło, host bazy, ścieżkę oraz hasło certyfikatu itp.)
*/
	private final LocalConfiuration conf;
/**
* Parametryczny konstruktor klasy DBConnector, jako parametr przyjmuje obiekt typu LocalConfiuration
* Wywołanie go powoduje: ustawienie pliku konfiguracyjnego na podany jako parametr, uruchomienie
* systemu do zarządzania bazą danych JDBC, ustawienie ścieżki do certyfikatu oraz hasła certyfikatu.
* @param conf
*/
	public DBConnector(LocalConfiuration conf) {
		this.conf = conf;
		try {
			Class.forName("com.mysql.jdbc.Driver").newInstance();
		} catch (Exception e) {
			System.err.println(e.getMessage());
		}
		System.setProperty("javax.net.ssl.trustStore", this.conf.getDBCertPath());
		System.setProperty("javax.net.ssl.trustStorePassword", this.conf.getDBCertPass());
	}
/**
* Metoda łącząca się w sposób bezpieczny (z wykorzystaniem SSL) z bazą danych
* @throws SQLException
*/
	public void connect() throws SQLException {
		String url = "jdbc:mysql://" + conf.getDBHost() + "/" + conf.getDBName() + "?useSSL=true&requireSSL=true";
		con = DriverManager.getConnection(url, conf.getDBUser(), conf.getDBPass());
	}
/**
* Metoda zamyka połączenie z baza danych
* @throws SQLException
*/
	public void close() throws SQLException {
		con.close();
	}
/**
* Metoda umieszcza w bazie danych znacznik czasowy timeStamp określony obiektem 'Date'
* Zwraca identyfikator rekordu.
* Wyrzuca wyjątek SQLException w razie niepowodzenia.
* @param timeStamp
* @return idVal
* @throws SQLException
*/
	public int insertTimeStamp(Date timeStamp) throws SQLException {
		int idVal = 0;
		PreparedStatement stmt = con.prepareStatement("call insertTime(?)");
		stmt.setTimestamp(1, new Timestamp(timeStamp.getTime()));
		ResultSet rs;
		try {
			stmt.execute();
			rs = stmt.getResultSet();
			if (rs.next()) {
				idVal = rs.getInt("id");
			}
			rs.close();
		} finally {
			stmt.close();
		}
		return idVal;
	}
	
	/** Metoda ustawia zmienną up w bazie danych na 1, aby watchdog pobrał informacje nt. nr telefonów, rodzajów zdarzeń itp. 
	* Funkcję wywołujemy na początku przy uruchomieniu programu, aby watchdog mógł dokonać lokalnej konfiguracji.
* @throws SQLException
	*/
	public void setUpdate() throws SQLException
        {
		PreparedStatement stmt = con.prepareStatement("call setUpdate()");
		try
                {
			stmt.execute();			
		}
                finally
                {
			stmt.close();
		}
	}
	
/**
* Metoda umieszcza w bazie danych aktualny znacznik czasowy
* Zwraca identyfikator rekordu.
* Wyrzuca wyjątek SQLException w razie niepowodzenia.
* @return int
* @throws SQLException
*/
	public int insertActualTimeStamp() throws SQLException {
		return insertTimeStamp(new Date());
	}
/**
* Metoda wstawia do bazy zdjęcie o odpowiednim identyfikatorze określonej kamery 
* oraz z określoną ścieżka do zdjęcia (wszystko podane jako parametr funkcji)
* W tym celu wykorzystuje metodę bazy danych insertPicture
*
* @param timeID
* @param cameraID
* @param filePath
* @throws SQLException
*/
	public void insertPicture(int timeID, int cameraID, String filePath)
			throws SQLException {
		PreparedStatement stmt = con.prepareStatement("call insertPicture(?, ?, ?);");
		stmt.setInt(1, timeID);
		stmt.setString(2, filePath);
		stmt.setInt(3, cameraID);
		try {
			stmt.execute();
		} finally {
			stmt.close();
		}
	}
/**
* Metoda wstawia do bazy temperaturę w postaci zmiennoprzecinkowej, o określonym identyfikatorze czasu, czujniku temperatury
* oraz z podaną wartością temperatury (wszystko podane jako parametr funkcji).
* Wyrzuca wyjątek SQLException w razie niepowodzenia.
* @param timeID
* @param sensorID
* @param temperature
* @throws SQLException
*/
	public void insertTemperature(int timeID, int sensorID, float temperature) throws SQLException {
		PreparedStatement stmt = con
				.prepareStatement("call insertTemperature(?, ?, ?);");
		stmt.setInt(1, timeID);
		stmt.setFloat(2, temperature);
		stmt.setInt(3, sensorID);
		try {
			stmt.execute();
		} finally {
			stmt.close();
		}
	}
/**
* Metoda wstawia do bazy stan karty o określonym identyfikatorze czasu, identyfikatorze czujnika kart,
* oraz z  odpowiednim stanem(wszystko podane jako parametr funkcji)
* Wyrzuca wyjątek SQLException w razie niepowodzenia.
* @param timeID
* @param cardReaderID
* @param state
* @throws SQLException
*/
	public void insertCardState(int timeID, int cardReaderID, String state) throws SQLException {
		PreparedStatement stmt = con.prepareStatement("call insertCardState(?, ?, ?);");
		stmt.setInt(1, timeID);
		stmt.setString(2, state);
		stmt.setInt(3, cardReaderID);
		try {
			stmt.execute();
		} catch (SQLException e) {
		} finally {
			stmt.close();
		}
	}
/**
* Metoda sprawdza czy użytkownik o określonym czytniku kart(cardReaderID), peselu(pesel) i pinie(pin) ma zezwolenie na dostep.
* Jeżeli tak to zwraca jego identyfikator z bazy danych, w przeciwnym razie zwraca wartość -1.
* Wyrzuca wyjątek SQLException w razie niepowodzenia.
* @param cardReaderID
* @param pesel
* @param pin
* @throws SQLException
*/
	public int verifyCard(int cardReaderID, String pesel, String pin) throws SQLException {
		PreparedStatement stmt = con.prepareStatement("call verifyCard(?, ?, ?);");
		stmt.setInt(1, cardReaderID);
		stmt.setString(2, pesel.trim());
		stmt.setString(3, pin.toLowerCase().trim());
		int idVal = -1;
		try {
			stmt.execute();
			ResultSet rs = stmt.getResultSet();
			if(rs.next()) {
				idVal = rs.getInt(1);
			}
		} finally {
			stmt.close();
		}
		return idVal;
	}
/**
* Metoda zwraca listę identyfikatorów aktywnych urządzeń (jednak w rzeczywistości zwraca identyfikatory wszystkiego, 
* co zostalo określone komendą SQL'owa podana jako parametr 'cmd').
* Metoda ta jest używana przez funkcje składowe takie jak: selectActiveSensors() , selectActiveCardReaders()
* Wyrzuca wyjątek SQLException w razie niepowodzenia.
* @param cmd
* @return result
* @throws SQLException
*/
	private Integer[] selectActiveDevices(String cmd) throws SQLException {
		Statement s = con.createStatement();
		s.execute(cmd);
		ResultSet rs = s.getResultSet();
		Vector<Integer> v = new Vector<Integer>();
		while (rs.next()) {
			v.add(rs.getInt("id"));
		}
		rs.close();
		s.close();
		Integer[] result = new Integer[v.size()];
		v.toArray(result);
		return result;
	}
/**
* Metoda zwraca mapę (para String->String) wszystkich aktywnych kamer
* Pierwszy string zawiera identyfikator (klucz) kamery, drugi natomiast ścieżkę do katalogu skojarzonego z kamerą
* Wyrzuca wyjątek SQLException w razie niepowodzenia.
* @return result
* @throws SQLException
*/
	public List<Map<String, String>> selectActiveCameras() throws SQLException {
		List<Map<String, String>> result = new ArrayList<Map<String,String>>();
		Statement s = con.createStatement();
		s.execute("call selectActiveCamera();");
		ResultSet rs = s.getResultSet();
		while (rs.next()) {
			Map<String, String> map = new HashMap<String, String>();
			map.put("camId", String.valueOf(rs.getInt("id")));
			map.put("path", rs.getString("dir_path"));
			result.add(map);
		}
		rs.close();
		s.close();
		
		return result;
	}
/**
* Metoda zwraca listę identyfikatorów aktywnych sensorów
* Wyrzuca wyjatek SQLException w razie niepowodzenia.
* @return Interger[]
* @throws SQLException
*/
	public Integer[] selectActiveSensors() throws SQLException {
		return selectActiveDevices("call selectActiveSensor();");
	}
/**
* Metoda zwraca listę identyfikatorów aktywnych czytników kart
* Wyrzuca wyjątek SQLException w razie niepowodzenia.
* @return Interger[]
* @throws SQLException
*/
	public Integer[] selectActiveCardReaders() throws SQLException {
		return selectActiveDevices("call selectActiveCardReader();");
	}
/**
* Metoda odczytuje wartość zmiennej up z bazy danych. Wartość 1 oznacza, że w baza danych została zmodyfikowana i niezbędne jest ponowne skonfigurowanie aplikacji.
* Wartośc 0 oznacza brak modyfikacji od ostatniego odczytu.
* Zwraca wyjatek SQLException w razie niepowodzenia.
* @return idVal
* @throws SQLException
*/	

	public boolean isUpdate() throws SQLException {
		PreparedStatement stmt = con.prepareStatement("call isUpdate();");
		int idVal = 0;
		try {
			stmt.execute();
			ResultSet rs = stmt.getResultSet();
			if (rs.next()) {
				idVal = rs.getInt("up");
			}
		} finally {
			stmt.close();
		}
		return idVal == 1;
	}
/**
* Metoda wybiera wszystkie telefony z bazy oraz wszystkie eventy, następnie 
* przyporządkowuje wszystkim eventom listę wszystkich numerów telefonów, 
* oraz ustawia pola eventów na takie, jakie są w bazie danych.
* @throws SQLException
* @see AlertEvents::getEventById(int id) 
*/		
	public void setEvens() throws SQLException {
		PreparedStatement stmt = con.prepareStatement("call selectEvent();");
		try {
			List<Long> phoneNumbers = getPhoneNumberForEvent();
			long numbers[] = new long[phoneNumbers.size()];
			for (int i = 0; i < phoneNumbers.size(); i++) {
				numbers[i] = phoneNumbers.get(i);
			}
			stmt.execute();
			ResultSet rs = stmt.getResultSet();
			while (rs.next()) {
				int id = rs.getInt("eventID");
				AlertEvents event = AlertEvents.getEventById(id);
				event.setMessage(rs.getString("message"));
				event.setAdditnionalArgument(rs.getString("arg"));
				event.setNumbers(numbers);
			}
		} finally {
			stmt.close();
		}
	}
/**
* Metoda zwraca listę typu long wszystkich numerów telefonów zapisanych w bazie
* @throws SQLException
* @return retVal
*/	
	private List<Long> getPhoneNumberForEvent() throws SQLException {
		PreparedStatement stmt = con.prepareStatement("call selectPhone();");
		List<Long> retVal = new ArrayList<Long>();
		try {
			stmt.execute();
			ResultSet rs = stmt.getResultSet();
			while (rs.next()) {
				retVal.add(rs.getLong("number"));
			}
		} finally {
			stmt.close();
		}
		return retVal;
	}

	public void readEigenfaces() throws SQLException {
		//if(true) return;

		String eigenfile = conf.getFRDirectory() + "/facedata.xml";

		byte[] content = null;
		PreparedStatement stmt = con.prepareStatement("call readEigenfaces()");
		ResultSet rs;
		try {
				stmt.execute();
				rs = stmt.getResultSet();
				if (rs.next()) {
						content = rs.getBytes("xml");
				}
				rs.close();

				try
				{
					FileOutputStream fos = new FileOutputStream(eigenfile);
					//fos.write(strContent.getBytes());
					fos.write(content);
					fos.close();
					System.out.println("Pobrano plik rozpoznawania twarzy...");
				} catch(FileNotFoundException e) {
					e.printStackTrace();
				} catch(IOException e) {
					e.printStackTrace();
				}
		} finally {
			stmt.close();
		}
	}
	
/**
* Metoda zwraca obiekt przechowujący konfigurację aplikacji.
* @return conf
* @see LocalConfiuration
*/
	public LocalConfiuration getConfiguration() {
		return conf;
	}

}
